<?php

/**
 * 微信服务商V3
 */
namespace app\api\controller\v3;



use app\Request;
use crmeb\services\app\MiniProgramService;
use crmeb\services\pay\Pay;
use think\facade\Db;
use app\services\car\BaseLogServices;
use GuzzleHttp\Exception\RequestException;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use WechatPay\GuzzleMiddleware\Util\MediaUtil;
use WechatPay\GuzzleMiddleware\Util\SensitiveInfoCrypto;
use WechatPay\GuzzleMiddleware\Auth\Signer;
use WechatPay\GuzzleMiddleware\Auth\SignatureResult;
use WechatPay\GuzzleMiddleware\Auth\WechatPay2Credentials;
use WechatPay\GuzzleMiddleware\Validator;
use GuzzleHttp\HandlerStack;
use Exception;
use think\log;

class WxPayController
{
    // 商户mchid
    private $mch_id ;
    private $sp_appid ;//服务商appid
    private $serial_no;//证书序列号 合作伙伴的
    private $mch_private_key = "";//私钥 合作伙伴的
    private $public_key_path;//支付平台公钥 合作伙伴的
    private $sub_appid ;
    private $notify_url = '';

    public function __construct()
    {
        $this->notify_url = "https://". $_SERVER['SERVER_NAME'].'/'.'api/notify/wx_notify';
        $this->tknotify_url = "https://". $_SERVER['SERVER_NAME'].'/'.'api/notify/refund_notify';
        $this->mch_private_key = public_path() . getPemPath(sys_config('pay_weixin_client_cert_v3'));//私钥 合作伙伴的  微信支付证书，在微信商家平台中可以下载！文件名一般为apiclient_cert.pem  服务商的
        $this->mch_id =sys_config('pay_weixin_mchid_v3');//服务商Mchid 1652272678
        $this->sp_appid=sys_config('routine_appId');//小程序appID wx35545e6faed1ad9b
        $this->pay_weixin_serial_no=sys_config('pay_weixin_serial_no');//V3服务商证书序列号「商户API证书」的「证书序列号」，可以在证书管理里面查看133ACD41C8CEB29C0B7407317E998358FB96FE48
    }


    //统一下单 jsapi
    public function unifyPay($openid, $out_trade_no, $total_fee, $body,$appid,$sub_mchid){
        $vendor= Db::name('wechat_pay_config')->where('id',$appid)->find();
        if(!$vendor)exception('商户不存在!');
        //这个数组里所有的数据都是必填的
        $unifydata = [
            'sp_appid' => $vendor['app_id'],//服务商小程序APPID
            'sp_mchid' => $vendor['sp_mchid'],//服务商号
//            'sub_appid' => $this->sub_appid,//选填字段【子商户/二级商户应用ID】 子商户/二级商户在开放平台申请的应用AppID，全局唯一。请求基础下单接口时请注意AppID的应用属性，例如公众号场景下，需使用应用属性为公众号的APPID
            //若sub_openid有传的情况下，sub_appid必填，且sub_appid需与sub_openid对应
            'sub_mchid' =>$sub_mchid,//进钱商户号
            'description' => $body,//订单详情
            'out_trade_no' => $out_trade_no,//订单号
            'notify_url' => $vendor['notify_url_v3'],//回调地址
            'settle_info'=>['profit_sharing'=>true],//是否分账
            'amount'=>['total'=>(int)$total_fee,'currency'=>'CNY'],
            'payer'=>['sp_openid'=>$openid],
            'scene_info'=>['payer_client_ip'=>$_SERVER["REMOTE_ADDR"]],
        ];
        $orderResult = $this->create_pay_order($unifydata,"mini");
        if(isset($orderResult['code'])){
            exception($orderResult['message']);
        }
        $timeStamp = time();
        $paydata = [
            'appId' => $vendor['app_id'],
            'timeStamp' => "$timeStamp",
            'nonceStr' => md5(rand(100000,999999)),
            'package' => 'prepay_id=' . $orderResult['prepay_id'],
        ];
        $sign = $this->getSign($paydata);//给小程序生成验证签名
        $paydata['signType'] = 'RSA';
        $paydata['paySign'] = $sign;
        return $paydata;
    }

    //APP支付
    public function WxappPay($out_trade_no, $total_fee,$body,$appid,$mchid){
        $vendor= Db::name('wechat_pay_config')->where('id',$appid)->find();
        if(!$vendor)exception('小程序主体不存在!');
        $unifydata = [
            'sp_appid' => $vendor['yny_app_id'],//服务商APP的appid
            'sp_mchid' => $vendor['sp_mchid'],//服务商户号
            'sub_mchid' => $mchid,//商户号
            'description' => $body,//订单详情
            'out_trade_no' => $out_trade_no,//订单号
            'notify_url' => $vendor['notify_url_v3'],//回调地址
            'settle_info'=>['profit_sharing'=>true],//是否分账
            'amount'=>['total'=>(int)$total_fee,'currency'=>'CNY'],
            'scene_info'=>['payer_client_ip'=>$_SERVER["REMOTE_ADDR"]],
        ];
        $orderResult = $this->create_pay_order($unifydata,"app");
        if(!isset($orderResult['prepay_id'])){
            exception('微信支付错误返回：' . $orderResult['message']);
        }
        $timeStamp = time();
        $paydata = [
            'appId' => $vendor['yny_app_id'],
            'partnerid'=> $mchid,
            'prepay_id'=>$orderResult['prepay_id'],
            'package' => 'Sign=WXPay',
            'timeStamp' => "$timeStamp",
            'nonceStr' => md5(rand(100000,999999)),
        ];
        $sign = $this->getappSign($paydata);//给APP生成验证签名
        $paydata['signType'] = 'RSA';
        $paydata['paySign'] = $sign;
        return $paydata;
    }
    //调用微信下单接口
    public function create_pay_order($inputData,$order_type){
        $inputData = json_encode($inputData);//v3微信传递参数需要json格式
        $url = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi";
        if($order_type == "app"){
            $url = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/app";
        }
        $headers = $this->getV3Sign($url,'POST',$inputData);//将签名放在报头里
        $result = $this -> post_data($url,$inputData,$headers);//下单
        return json_decode($result,true);
    }


    //获取签名 （用于下单create_pay_order接口）
    public function getV3Sign($url,$http_method,$body) {
        //商户号
        $mchid = $this->mch_id;
        //随机字符串
        $nonce = strtoupper($this -> getNoncestr());
        //商户序列号
        $serialNo = $this->pay_weixin_serial_no;
        //时间戳
        $timestamp = time();
        //url
        $url_parts = parse_url($url);
        //获取绝对路径
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        //密钥key
        $private_key = $this->getPrivateKey($this->mch_private_key);
        //拼接参数
        $message = $http_method."\n".
            $canonical_url."\n".
            $timestamp."\n".
            $nonce."\n".
            $body."\n";
        //计算签名值
        openssl_sign($message, $raw_sign, $private_key, 'sha256WithRSAEncryption');
        $sign   = base64_encode($raw_sign);
//        $token  = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',$mchid, $nonce, $timestamp, $serial_no, $sign);
        $token = sprintf('WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $mchid, $nonce, $timestamp, $serialNo, $sign);
        $headers = [
            'Accept: application/json',
            'User-Agent: */*',
            'Content-Type: application/json; charset=utf-8',
            'Authorization: '.$token,
        ];

        return $headers;
    }

    //生成32位随机字符串 （用于getV3Sign获取签名接口、用于没用到的refund退款接口、用于wx_refund退款接口
    public function getNoncestr($length = 32) {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
    //获取私钥
    public function getPrivateKey($filepath) {
        return openssl_get_privatekey(file_get_contents($filepath));
    }
    //POST调用API
    public function post_data($url,$data=[],$headers=[]){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        //设置header头
        curl_setopt($ch, CURLOPT_HTTPHEADER,$headers);
        // POST数据
        curl_setopt($ch, CURLOPT_POST, 1);
        // 把post的变量加上
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        $output = curl_exec($ch);
        curl_close($ch);
        return $output;
    }

    //生成签名 给小程序生成验证签名 用于小程序调起支付的签名
    public function getSign($data) {
        $tmpstr = $data['appId'] . "\n" . $data['timeStamp'] . "\n" . $data['nonceStr'] . "\n" . $data['package'] . "\n";
        $privateKey         = file_get_contents($this->mch_private_key);
        $binary_signature   = "";
        $algo               = "SHA256";
        openssl_sign($tmpstr, $binary_signature, $privateKey, $algo);
        $sign               = base64_encode($binary_signature);
        return $sign;
    }
    //生成签名 给APP生成验证签名 用于APP调起支付的签名
    public function getappSign($data) {
        $tmpstr = $data['appId'] . "\n" . $data['timeStamp'] . "\n" . $data['nonceStr'] . "\n" . $data['prepay_id'] . "\n";
        $privateKey         = file_get_contents($this->mch_private_key);
        $binary_signature   = "";
        $algo               = "SHA256";
        openssl_sign($tmpstr, $binary_signature, $privateKey, $algo);
        $sign               = base64_encode($binary_signature);
        return $sign;
    }


    //微信签名 - v3版本退款用
    function get_tk_Sign($data=array(),$url,$randstr,$time){
        $str = "POST"."\n".$url."\n".$time."\n".$randstr."\n".$data."\n";
        $privateKey = file_get_contents($this->mch_private_key);//在商户平台下载的秘钥,读取到变量
        $binary_signature = "";
        $algo = "SHA256";
        openssl_sign($str, $binary_signature, $privateKey, $algo);
        $sign = base64_encode($binary_signature);
        return $sign;
    }


    public  function postXmlCurl($xml, $url, $second = 60, $useCert = false)
    {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //严格校验
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

        if ($useCert == true) {
            //设置证书
            //使用证书：cert 与 key 分别属于两个.pem文件
            //证书文件请放入服务器的非web目录下
            $sslCertPath = "./static/cert/apiclient_cert.pem";
            $sslKeyPath = "./static/cert/apiclient_key.pem";
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLCERT, $sslCertPath);
            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLKEY, $sslKeyPath);
        }
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
        curl_setopt($ch, CURLOPT_TIMEOUT, 40);
        set_time_limit(0);

        //运行curl
        $data = curl_exec($ch);

        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            throw new WxPayException("curl出错，错误码:$error");
        }
    }

    //数组转换成xml
    public function arrayToXml($arr)
    {
        $xml = "<root>";
        foreach ($arr as $key => $val) {
            if (is_array($val)) {
                $xml .= "<" . $key . ">" . $this->arrayToXml($val) . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            }
        }
        $xml .= "</root>";
        return $xml;
    }

    //xml转换成数组
    public function xmlToArray($xml)
    {
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        $val = json_decode(json_encode($xmlstring), true);
        return $val;
    }



    //退款
    public function wx_refund($orderNo, $totalFee, $refundFee,$out_refund_no,$vendorid){ //商户订单号,微信生成的退款订单号 二选一即可
        $wx_applyment = Db::name('wx_applyment')->where('id',$vendorid)->find();
        if(!$wx_applyment)app('json')->fail('商户数据异常');
        $refundData=[
            'sub_mchid'=>$wx_applyment['mchid'],
            'transaction_id'=>$orderNo,//微信支付单号
            'out_refund_no'=>$out_refund_no,//商户系统内部的退款单号
//            'reason'=>$refundReason,
//            'notify_url'=>$this->tknotify_url,
            'funds_account'=>'AVAILABLE',
            'amount'=>[
                'refund'=>(int)$refundFee, //退款标价金额，单位为分，可以做部分退款
                'total'=>(int)$totalFee, //订单总金额，单位为分
                'currency'=>'CNY'
            ]
        ];
        $url='https://api.mch.weixin.qq.com/v3/refund/domestic/refunds';
        $urlarr = parse_url($url); //拆解为：[scheme=>https,host=>api.mch.weixin.qq.com,path=>/v3/pay/transactions/native]
        $mchid = $this->mch_id;//商户ID
        $xlid = $this->pay_weixin_serial_no;//证书序列号
        $refundData=json_encode($refundData);
        $nonce = $this->getNonceStr();
        $time=time();
        $key = $this->get_tk_Sign($refundData,$urlarr['path'],$nonce,$time);
        $token = sprintf('mchid="%s",serial_no="%s",nonce_str="%s",timestamp="%d",signature="%s"',$mchid,$xlid,$nonce,$time,$key);
        $header  = array(
            'Accept: application/json',
            'Content-Type: application/json',
            'User-Agent:*/*',
            'Authorization: WECHATPAY2-SHA256-RSA2048 '.$token
        );
        $res=$this->post_data($url,$refundData,$header);
        $res_array=json_decode($res,true);
        if(isset($res_array['status'])){
            if(($res_array['status']=='PROCESSING' || $res_array['status']=='SUCCESS') && isset($res_array['status'])){
                return ['code'=>1,'msg'=>'退款成功'];
            }else{
                return ['code'=>0,'msg'=>$res_array['message']];
            }
        } else{
            return ['code'=>0,'msg'=>$res_array['message']];
        }
    }



    //支付回调接口，没有用到因为sodium扩展没开起来
    public function notify(){
        $result = $this->request->param();
        if($result){
            $text = base64_decode($result['resource']['ciphertext']); //解密
            /* =========== 使用V3支付需要PHP7.2.6安装sodium扩展才能进行解密参数  ================ */
            $str = sodium_crypto_aead_aes256gcm_decrypt($text, $result['resource']['associated_data'], $result['resource']['nonce'], $this->apiV3Key);
            $res = json_decode($str, true);
            //如果成功返回了
            if($res['trade_state'] == 'SUCCESS'){
                Db::startTrans();
                try {
                    Db::name('service_order')->where('orderno',$res['out_trade_no'])->update([
                        'pay_state'=>1,
                        'transaction_id'=>$res['transaction_id'],
                        'pay_time'=>time(),
                        'status'=>1
                    ]);
                    $lid = Db::name('service_order')->where('orderno',$res['out_trade_no'])->value('lid');
                    if($lid){
                        Db::name('service_list')->where('id',$lid)->setInc('sales',1);
                    }
                    Db::commit();
                    return $this->json_result(200,'支付成功');
                }catch (DbException $e){
                    return $this->json_result(400,$e->getMessage());
                }
            }
        }
        return $this->json_result(400,'支付失败');
    }


}
